package com.margiel.rozkladowka;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import com.margiel.rozkladowka.costs.ConnectionType;
import com.margiel.rozkladowka.costs.Day;
import com.margiel.rozkladowka.costs.TTConst;
import com.margiel.rozkladowka.model.Stop;
import com.margiel.rozkladowka.model.TimeTable;
import com.margiel.rozkladowka.model.Vehicle;
import com.margiel.util.EscapeChars;
import com.margiel.util.FileUtil;
import com.margiel.util.StringUtil;
 
public class RozkladowkaMaker { 
	private final Logger log = Logger.getLogger(RozkladowkaMaker.class);
	private File propertyFile;
	private Properties properties;
	private static final Map<String, Pattern> patterns = new HashMap<String, Pattern>(); 
	
	public RozkladowkaMaker(String path) throws IOException{
		propertyFile = new File(path);
		this.properties = new Properties();
		this.properties.load(new FileInputStream(propertyFile));
	}
	
	public RozkladowkaMaker() {
	}
	
	public static void main(String[] args) {
		
	}
	
	public String getRootFolder(){
		return properties.getProperty("root.folder");
	}
	public String getLinesFile(){
		return properties.getProperty("root.lines.file");
	}
	public String getLineLinkPattern(){
		return properties.getProperty("root.lines.file.lineLinkPattern");
	}
	public String getDirectionLinkPattern(){
		return properties.getProperty("root.lines.direction.linkPattern");
	}
	public String getBusstopLinkPattern(){
		return properties.getProperty("root.lines.busstop.linkPattern");
	}
	public String getTimeTableLinePattern(){
		return properties.getProperty("root.lines.timeTable.linePattern");
	}
	public Pattern getMinuteSeparator(){
		Pattern pattern = patterns.get("root.lines.timeTable.minuteSeparator");
		if (pattern == null){
			pattern = Pattern.compile(properties.getProperty("root.lines.timeTable.minuteSeparator"));
		}
		return pattern;
	}
	
	public List<Vehicle> getLines() throws MalformedURLException, IOException {
		log.info("[getLines], entering...");
		String pattern = getLineLinkPattern();
		CleanerObject cleanerObject = new CleanerObject();
		pattern = processPattern(pattern, cleanerObject, TTConst.NO, TTConst.LINK);
		String link = FileUtil.getLink(getDataSource(), getRootFolder(), getLinesFile());
		List<Vehicle> vehicles = new ArrayList<Vehicle>(50);
		Map<String, Map<String, String>> noToKeyToValue = getData(link, pattern, TTConst.NO, cleanerObject);
		for (Map.Entry<String, Map<String,String>> resultLine : noToKeyToValue.entrySet()) {
			String no = resultLine.getValue().get(TTConst.NO.getToken()).intern();
			no = StringUtil.cleanPolish(no);
			Vehicle v = new Vehicle(no, resultLine.getValue().get(TTConst.LINK.getToken()));
			vehicles.add(v);
		}
		return vehicles;
	}

	private Map<String, Map<String, String>> getData(String link, String pattern, TTConst mainToken, CleanerObject cleanerObject) throws MalformedURLException, IOException {
		Pattern compiledPattern = patterns.get(pattern);
		if (compiledPattern == null){
			compiledPattern = Pattern.compile(pattern);
		}
		return getData(link, compiledPattern, mainToken, cleanerObject);
	}
	private ConnectionType getDataSource() {
		String ds = properties.getProperty("root.dataSource");
		if (ds.equals("URL")){
			return ConnectionType.URL;
		}else if (ds.equals("FILE")){
			return ConnectionType.FILE;
		}
		return null;
	}

	private Map<String, Map<String, String>> getData(String link, Pattern pattern, TTConst mainToken, CleanerObject cleanerObject) throws MalformedURLException, IOException {
		InputStream inputStream = null;
		if(getDataSource() == ConnectionType.FILE){
			inputStream = new FileInputStream(new File(link));
		}
		else if(getDataSource() == ConnectionType.URL){
			inputStream = new URL(link).openStream();
		}
		Scanner scanner = new Scanner(inputStream);
		Map<String, Map<String, String>> resultMap = new TreeMap<String, Map<String, String>>();
		Integer lineNo=0;
		while (scanner.hasNextLine()) {
			String token = "";
			while ((token = scanner.findInLine(pattern)) != null) {
				Scanner scanner2 = new Scanner(token);
				scanner2.findInLine(pattern);
				MatchResult mr = scanner2.match();
				Map<String, String> lineMap = new HashMap<String, String>();
				for (String key : cleanerObject.getKeys()) {
					String value = mr.group(cleanerObject.getIndex(key));
					log.debug("putting in map ["+key+"]["+value+"]");
					lineMap.put(key, value);
				}
				if (mainToken == null){
					log.debug("putting into resultMap["+lineNo+"]");
					resultMap.put(lineNo.toString(), lineMap);
					lineNo++;
				}else{
					log.debug("putting into resultMap["+lineMap.get(mainToken.getToken())+"]");
					resultMap.put(lineMap.get(mainToken.getToken()), lineMap);
				}
				scanner2.close();
			}
			scanner.nextLine();
		}
		scanner.close();
		try{
			inputStream.close();
		}catch(Exception ex){
			log.error("error during closing connection, ", ex);
		}
		return resultMap;
	}
	
	private String processPattern(String pattern, CleanerObject cleanerObject, TTConst... tokens){
		for (TTConst token : tokens) {
			pattern = replaceTokenWithRegExp(pattern, token.getToken(), cleanerObject);
		}
		log.debug("returning pattern=["+pattern+"]");
		if (patterns.get(pattern) == null){
			patterns.put(pattern, Pattern.compile(pattern));
		}
		return pattern;
	}
	
	private String replaceTokenWithRegExp(String pattern, String token, CleanerObject cleanerObject){
		log.debug("processing pattern["+pattern+"], token["+token+"]");
		int tokenIndex = pattern.indexOf(token);
		cleanerObject.addKey(token, tokenIndex);
		String nextChar = String.valueOf(pattern.charAt(tokenIndex+token.length()));
		log.debug("next char=["+nextChar+"]");
		nextChar =  EscapeChars.forRegex(nextChar);
		log.debug("next char after escaping=["+nextChar+"]");
		String linkRegExp = "([^"+nextChar+"]+)";
		String[] array = pattern.split(EscapeChars.forRegex(token));
		pattern = array[0]+linkRegExp+array[1];
		return pattern;
	}
	
	public void getWays(Vehicle vehicle) throws MalformedURLException, IOException{
		
		if (StringUtil.isEmpty(vehicle.getLink())){
			log.warn("no link for vehicle=["+vehicle.getNo()+"]");
			return;
		}
		String directionLink = FileUtil.getLink(getDataSource(), getRootFolder()+vehicle.getLink());
		File direcionFile = new File(getRootFolder()+vehicle.getLink());
		log.debug("directionFile =["+direcionFile.getAbsolutePath()+"]");//, "+(direcionFile.exists() ? "" : " NOT ") +"EXISTS");
		log.debug("IN pattern =["+getDirectionLinkPattern()+"]");
		CleanerObject cleanerObject = new CleanerObject();
		String pattern = processPattern(getDirectionLinkPattern(), cleanerObject, TTConst.LINK, TTConst.DST, TTConst.SRC);
		log.debug("OUT pattern =["+pattern+"]");
		Map<String, Map<String, String>> resultMap = getData(directionLink, pattern, null, cleanerObject);
//		log.debug(resultMap); 
		for (Map<String, String> element : resultMap.values()) {
			String srcStop = StringUtil.trimAll(element.get(TTConst.SRC.getToken()));
			srcStop = StringUtil.cleanPolish(srcStop);
			String dstStop = StringUtil.trimAll(element.get(TTConst.DST.getToken()));
			dstStop = StringUtil.cleanPolish(dstStop);
			String link = StringUtil.trimAll(element.get(TTConst.LINK.getToken()));
			TimeTable tt = new TimeTable(srcStop.intern(), dstStop.intern(), link);
			vehicle.addTimeTable(tt);
		}
	}
	
	public void getStops(Vehicle vehicle) throws MalformedURLException, IOException{
		for (TimeTable timeTable : vehicle.getTimeTables()) {
			if (StringUtil.isEmpty(timeTable.getDirectionLink())) {
				log.warn("no link for timetable=[" + timeTable.getSourceStop() + timeTable.getDestinationStop() + "]");
				return;
			}
			String wayLink = FileUtil.getLink(getDataSource(), getRootFolder(), vehicle.getLink(), timeTable.getDirectionLink());
//			File wayFile = FileUtil.getFile(getRootFolder(), vehicle.getLink(), timeTable.getDirectionLink());
			log.debug("wayFile =["+wayLink+"]");//, "+(wayFile.exists() ? "" : " NOT ") +"EXISTS ");
			String pattern = getBusstopLinkPattern();
			CleanerObject cleanerObject = new CleanerObject();
			pattern = processPattern(pattern, cleanerObject, TTConst.LINK, TTConst.BUSSTOP);
			Map<String, Map<String, String>> resultMap = getData(wayLink, pattern, null, cleanerObject);
			for (Map<String, String> result : resultMap.values()) {
				String name = result.get(TTConst.BUSSTOP.getToken());
				String link = result.get(TTConst.LINK.getToken());
				log.debug("Stop name=["+name+"], link=["+link+"]");
				String stopName = StringUtil.trimAll(name).intern();
				stopName = StringUtil.cleanPolish(stopName);
				Stop stop = new Stop(stopName, StringUtil.trimAll(link).intern()); 
				timeTable.addStop(stop);
			}
		}
	}
	public void getTimeTable(Vehicle vehicle) throws MalformedURLException, IOException{
		String pattern = getTimeTableLinePattern();
		CleanerObject cleanerObject = new CleanerObject();
		pattern = processPattern(pattern, cleanerObject, TTConst.HOUR, TTConst.MINUTE, TTConst.HOUR_H, TTConst.MINUTE_H);
		for (TimeTable timeTable : vehicle.getTimeTables()) {
			for (Stop stop : timeTable.getStops()) {
				String link = FileUtil.getLink(getDataSource(), getRootFolder(), vehicle.getLink(), timeTable.getDirectionLink(), stop.getLink());
//				File linkFile = FileUtil.getFile(getRootFolder(), vehicle.getLink(), timeTable.getDirectionLink(), stop.getLink()); 
				log.debug("linkFile =["+link+"]");//, "+(linkFile.exists() ? "" : " NOT ") +"EXISTS ");
				Map<String, Map<String, String>> resultMap = getData(link, pattern, null, cleanerObject);
				for (Map<String, String> resultLine : resultMap.values()) {
					Number hour = getHour(resultLine.get(TTConst.HOUR.getToken()));
					Number hourHoliday = getHour(resultLine.get(TTConst.HOUR_H.getToken()));
					String[] minutes = getMinutes(resultLine.get(TTConst.MINUTE.getToken()));
					String[] minutesHoliday = getMinutes(resultLine.get(TTConst.MINUTE_H.getToken()));
					if (hour != null){
						stop.addTime(Day.WEEK, hour, minutes);
					}
					if (hourHoliday != null){
						stop.addTime(Day.HOLIDAY, hourHoliday, minutesHoliday);
					}
				}
			}
		}
	}
	private Number getHour(String hourString){
		log.debug("[getHour], entering...");
		hourString = StringUtil.trimAll(hourString);
		if (!StringUtil.isEmpty(hourString)){
			return Byte.valueOf(Byte.parseByte(hourString));
		}else{
			return null;
		}
	}
	
	private String[] getMinutes(String minutes){
		log.debug("[getMinutes], entering...");
		minutes = StringUtil.trimAll(minutes);
		if (!StringUtil.isEmpty(minutes)){
			return StringUtil.split(minutes, getMinuteSeparator());
//			return minutes.split(getMinuteSeparator());
		}else{
			return null;
		}
	}
}
